package ar.edu.uba.fi.jmit.aspecter;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtClass;
import ar.edu.uba.fi.jmit.aspecter.core.ResourceProperties;
import ar.edu.uba.fi.jmit.aspecter.core.WeavingInstrumentor;
import ar.edu.uba.fi.jmit.aspecter.core.WeavingInstrumentorImpl;
import ar.edu.uba.fi.jmit.aspecter.core.interceptor.Interceptor;
import ar.edu.uba.fi.jmit.aspecter.testUtils.ExceptionAfterMethodInterceptor;
import ar.edu.uba.fi.jmit.aspecter.testUtils.ExceptionInjection;
import ar.edu.uba.fi.jmit.aspecter.testUtils.PublicWeavingInstrumentor;
import ar.edu.uba.fi.jmit.aspecter.testUtils.TestUtils;

public class WeavingInstrumentorTestCase extends AspecterTestCase {

	public void testApplyInjectors() {
		WeavingInstrumentor instrumentor = new WeavingInstrumentorImpl();
		try {
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.Point", getNextClassName());
			ExceptionInjection injector = new ExceptionInjection("someMethod", String.class);
			injector.addParameterTypes(new Class[]{});
			instrumentor.addInjector(ctClass.getName(), injector);
			instrumentor.alter(ctClass);
			Class<?> clazz = ctClass.toClass();
			Object point = clazz.newInstance();
			Method someMethodNoParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{});
			assertNotNull(someMethodNoParams);
			try {
				someMethodNoParams.invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
		}catch(Exception e) {
			fail(e);
		}
	}
	
	public void testApplyInterceptors() {
		WeavingInstrumentor instrumentor = new WeavingInstrumentorImpl();
		try {
			Interceptor interceptor = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getY");
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			instrumentor.addInterceptor(interceptor);
			instrumentor.alter(ctClass);
			Class<?> clazz = ctClass.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("getX").invoke(tp6);
			clazz.getMethod("setX", int.class).invoke(tp6, 1);
			clazz.getMethod("setY", int.class).invoke(tp6, 2);
			
			try{
				clazz.getMethod("getY").invoke(tp6);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
		}catch(Exception e) {
			fail(e);
		}
	}
	
	public void testApplyAll() {
		WeavingInstrumentor instrumentor = new WeavingInstrumentorImpl();
		try {
			Interceptor interceptor = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getY");
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			instrumentor.addInterceptor(interceptor);
			
			ExceptionInjection injector = new ExceptionInjection("setY", void.class);
			injector.addParameterTypes(new Class[]{int.class});
			instrumentor.addInjector(ctClass.getName(), injector);
			
			instrumentor.alter(ctClass);
			Class<?> clazz = ctClass.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("getX").invoke(tp6);
			clazz.getMethod("setX", int.class).invoke(tp6, 1);
			try{
				clazz.getMethod("getY").invoke(tp6);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			
			try{
				clazz.getMethod("setY", int.class).invoke(tp6, 2);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
		}catch(Exception e) {
			fail(e);
		}
	}
	
	public void testApplyMany() {
		WeavingInstrumentor instrumentor = new WeavingInstrumentorImpl();
		try {
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			Interceptor interceptorGetY = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getY");
			instrumentor.addInterceptor(interceptorGetY);
			
			Interceptor interceptorGetX = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getX");
			instrumentor.addInterceptor(interceptorGetX);
			
			ExceptionInjection injectorSetY = new ExceptionInjection("setY", void.class);
			injectorSetY.addParameterTypes(new Class[]{int.class});
			instrumentor.addInjector(ctClass.getName(), injectorSetY);
			
			ExceptionInjection injectorSetX = new ExceptionInjection("setX", void.class);
			injectorSetX.addParameterTypes(new Class[]{int.class});
			instrumentor.addInjector(ctClass.getName(), injectorSetX);
			
			ExceptionInjection injectorSetX2 = new ExceptionInjection("setX", void.class);//apply to all "setX" methods. Will inject twice the same code
			instrumentor.addInjector(ctClass.getName(), injectorSetX2);
			
			instrumentor.alter(ctClass);
			Class<?> clazz = ctClass.toClass();
			Object tp6 = clazz.newInstance();
			try{
				clazz.getMethod("getX").invoke(tp6);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			try{
				clazz.getMethod("getY").invoke(tp6);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			
			try{
				clazz.getMethod("setY", int.class).invoke(tp6, 2);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
			
			try{
				clazz.getMethod("setX", int.class).invoke(tp6, 2);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
		}catch(Exception e) {
			fail(e);
		}
	}
	
	public void testAterMany() {
		WeavingInstrumentor instrumentor = new WeavingInstrumentorImpl();
		try {
			CtClass ctClassPoint3 = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			CtClass ctClassPoint4 = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", getNextClassName());
			Interceptor interceptor = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getY");
			instrumentor.addInterceptor(interceptor);
			
			ExceptionInjection injector = new ExceptionInjection("setY", void.class);
			injector.addParameterTypes(new Class[]{int.class});
			instrumentor.addInjector(ctClassPoint3.getName(), injector);
			
			instrumentor.alter(ctClassPoint3);
			instrumentor.alter(ctClassPoint4);
			Class<?> clazz = ctClassPoint3.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("getX").invoke(tp6);
			clazz.getMethod("setX", int.class).invoke(tp6, 1);
			try{
				clazz.getMethod("getY").invoke(tp6);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			
			try{
				clazz.getMethod("setY", int.class).invoke(tp6, 2);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
			/*
			 * Non of this should throw exception...
			 */
			Class<?> clazzPoint4 = ctClassPoint4.toClass();
			Object tp4 = clazzPoint4.newInstance();
			clazzPoint4.getMethod("getX").invoke(tp4);
			clazzPoint4.getMethod("setX", int.class).invoke(tp4, 1);
			clazzPoint4.getMethod("getY").invoke(tp4);
			clazzPoint4.getMethod("setY", int.class).invoke(tp4, 2);
		}catch(Exception e) {
			fail(e);
		}
	}
	
	public void testSendEventMethod() {
		PublicWeavingInstrumentor instrumentor = new PublicWeavingInstrumentor();
		try {
			CtClass ctClassPoint3 = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest1", getNextClassName());
			instrumentor.addSendEventMethod(ctClassPoint3);
			Class<?> clazz = ctClassPoint3.toClass();
			for(Method method: clazz.getMethods()) {
				System.out.println(method.getName());
			}
			assertNotNull(clazz.getMethod(ResourceProperties.getProperty("jmit.aspecter.eventMethodName"), Class.forName(ResourceProperties.getProperty("jmit.sender.eventClassName"))));
			
		} catch (Exception e) {
			fail(e);
		}
		
	}
}
